#include "vsf.h"
#include "vsf_fatfs.h"

#ifndef VSF_FATFS_CFG_MAXVOLUME
#define VSF_FATFS_CFG_MAXVOLUME		FF_VOLUMES
#endif

// fatfs port
static struct vsf_fatfs_internal_t
{
	struct
	{
		struct vsfmal_t *mal;
#if FF_FS_REENTRANT
		struct vsfsm_crit_t crit;
#endif
	} disk[VSF_FATFS_CFG_MAXVOLUME];
	uint32_t mskarr[(VSF_FATFS_CFG_MAXVOLUME + 31) >> 5];
} vsf_fatfs;

DSTATUS disk_status(BYTE pdrv)
{
	if (pdrv >= VSF_FATFS_CFG_MAXVOLUME) return STA_NOINIT;
	return mskarr_get(vsf_fatfs.mskarr, pdrv) ? 0 : STA_NOINIT;
}

DSTATUS disk_initialize(BYTE pdrv)
{
	if (disk_status(pdrv))
		return STA_NOINIT;
	else
	{
		struct vsfmal_t *mal = vsf_fatfs.disk[pdrv].mal;
		struct vsfsm_pt_t pt =
		{
			.sm = &(vsfsm_thread_get_cur())->sm,
			.state = 0,
			.user_data = mal,
		};
		vsfsm_evt_t evt = VSFSM_EVT_NONE;
		vsf_err_t err;

		while (1)
		{
			err = vsfmal_init(&pt, evt);
			if (!err) break;
			else if (err < 0) return STA_NOINIT;
			else evt = vsfsm_thread_wait();
		}
	}
	return FR_OK;
}

DRESULT disk_read(BYTE pdrv, BYTE *buff, DWORD sector, UINT count)
{
	if (disk_status(pdrv))
		return RES_PARERR;
	else
	{
		struct vsfmal_t *mal = vsf_fatfs.disk[pdrv].mal;
		struct vsfsm_pt_t pt =
		{
			.sm = &(vsfsm_thread_get_cur())->sm,
			.state = 0,
			.user_data = mal,
		};
		vsfsm_evt_t evt = VSFSM_EVT_NONE;
		vsf_err_t err;

		sector *= mal->cap.block_size;
		count *= mal->cap.block_size;
		while (1)
		{
			err = vsfmal_read(&pt, evt, sector, buff, count);
			if (!err) break;
			else if (err < 0) return STA_NOINIT;
			else evt = vsfsm_thread_wait();
		}
	}
	return FR_OK;
}

DRESULT disk_write(BYTE pdrv, const BYTE *buff, DWORD sector, UINT count)
{
	if (disk_status(pdrv))
		return RES_PARERR;
	else
	{
		struct vsfmal_t *mal = vsf_fatfs.disk[pdrv].mal;
		struct vsfsm_pt_t pt =
		{
			.sm = &(vsfsm_thread_get_cur())->sm,
			.state = 0,
			.user_data = mal,
		};
		vsfsm_evt_t evt = VSFSM_EVT_NONE;
		vsf_err_t err;

		sector *= mal->cap.block_size;
		count *= mal->cap.block_size;
		while (1)
		{
			err = vsfmal_write(&pt, evt, sector, (uint8_t *)buff, count);
			if (!err) break;
			else if (err < 0) return STA_NOINIT;
			else evt = vsfsm_thread_wait();
		}
	}
	return FR_OK;
}

DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff)
{
	if (disk_status(pdrv))
		return RES_PARERR;
	else
	{
		struct vsfmal_t *mal = vsf_fatfs.disk[pdrv].mal;
		switch (cmd)
		{
		case CTRL_SYNC:
			break;
		case GET_SECTOR_COUNT:
			*(DWORD *)buff = mal->cap.block_num;
			break;
		case GET_SECTOR_SIZE:
			*(WORD *)buff = mal->cap.block_size;
			break;
		}
	}
	return FR_OK;
}

int vsf_fatfs_add_disk(struct vsfmal_t *mal)
{
	uint8_t origlevel = vsfsm_sched_lock();
	int idx = mskarr_ffz(vsf_fatfs.mskarr, VSF_FATFS_CFG_MAXVOLUME);
	if (idx >= 0)
	{
		mskarr_set(vsf_fatfs.mskarr, idx);
		vsf_fatfs.disk[idx].mal = mal;
	}
	vsfsm_sched_unlock(origlevel);
	return idx;
}

void vsf_fatfs_remove_disk(struct vsfmal_t *mal)
{
	for (int i = 0; i < VSF_FATFS_CFG_MAXVOLUME; i++)
	{
		if (mskarr_get(vsf_fatfs.mskarr, i) && (mal == vsf_fatfs.disk[i].mal))
		{
			mskarr_clr(vsf_fatfs.mskarr, i);
			break;
		}
	}
}

#if FF_USE_LFN == 3	/* Dynamic memory allocation */
void *ff_memalloc(UINT msize) {return vsf_bufmgr_malloc(msize);}
void ff_memfree(void *mblock) {vsf_bufmgr_free(mblock);}
#endif

#if FF_FS_REENTRANT	/* Mutal exclusion */
int ff_cre_syncobj(BYTE vol, FF_SYNC_t *sobj)
{
	struct vsfsm_crit_t *crit;
	if (disk_status(vol))
		return 0;

	crit = &vsf_fatfs.disk[vol].crit;
	vsfsm_crit_init(crit, VSFSM_EVT_USER);
	if (sobj) *sobj = crit;
	return 1;
}

int ff_del_syncobj(FF_SYNC_t sobj)
{
	return 1;
}

int ff_req_grant(FF_SYNC_t sobj)
{
	if (vsfsm_crit_enter(sobj, &vsfsm_thread_get_cur()->sm))
		vsfsm_thread_wfe(VSFSM_EVT_USER);
	return 1;
}

void ff_rel_grant(FF_SYNC_t sobj)
{
	vsfsm_crit_leave(sobj);
}

#endif

// vsf fs port
static FATFS __fatfs;
static void vsf_fatfs_on_finish(struct vsfsm_thread_t *thread)
{
	struct vsf_fatfs_t *fatfs = (struct vsf_fatfs_t *)thread->priv;
	vsfsm_post_evt_pending(fatfs->pending_sm, VSFSM_EVT_USER);
}

void vsf_fatfs_run_mount(struct vsfsm_thread_t *thread)
{
	struct vsf_fatfs_t *fatfs = (struct vsf_fatfs_t *)thread->priv;
	int idx = vsf_fatfs_add_disk(fatfs->mal);
	char volume[3];

	if (idx < 0)
		return;

	volume[0] = '0' + idx;
	volume[1] = ':';
	volume[2] = '\0';
	if (f_mount(&__fatfs, volume, 1) || f_opendir(&fatfs->root, volume))
		fatfs->root.obj.fs = NULL;
}

static vsf_err_t vsf_fatfs_mount(struct vsfsm_pt_t *pt, vsfsm_evt_t evt,
		struct vsfile_t *dir)
{
	struct vsf_fatfs_t *fatfs = (struct vsf_fatfs_t *)pt->user_data;

	vsfsm_pt_begin(pt);
	vsfsm_crit_init(&fatfs->crit, VSFSM_EVT_USER);
	vsfsm_crit_enter(&fatfs->crit, pt->sm);

	fatfs->thread.op.on_terminate = vsf_fatfs_on_finish;
	fatfs->thread.stack_size = sizeof(fatfs->stack);
	fatfs->thread.stack = fatfs->stack;

	fatfs->pending_sm = pt->sm;
	fatfs->thread.priv = fatfs;
	fatfs->thread.op.on_run = vsf_fatfs_run_mount;
	vsfsm_thread_start(&fatfs->thread);
	vsfsm_pt_wfe(pt, VSFSM_EVT_USER);

	vsfsm_crit_leave(&fatfs->crit);
	vsfsm_pt_end(pt);
	return !fatfs->root.obj.fs ? VSFERR_FAIL : VSFERR_NONE;
}

void vsf_fatfs_run_getchild(struct vsfsm_thread_t *thread)
{
	struct vsf_fatfs_t *fatfs = (struct vsf_fatfs_t *)thread->priv;
	char *name = fatfs->getchild.name;
	DIR *dir = fatfs->getchild.dir;
	FILINFO fno;

	fatfs->getchild.file = NULL;
	while (1)
	{
		if (f_readdir(dir, &fno) || !fno.fname[0])
			break;

		if (vsfile_match(name, fno.fname))
		{
			struct vsf_fatfs_file_t *file;
			uint32_t fname_len = strlen(fno.fname) + 1;
			uint32_t entry_len = fno.fattrib & AM_DIR ? sizeof(DIR) : sizeof(FIL);
			uint32_t size = sizeof(*file) + entry_len + fname_len;

			file = vsf_bufmgr_malloc(size);
			if (file != NULL)
			{
				file->entry.ptr = &file[1];
				file->file.name = (char *)file->entry.ptr + entry_len;

				strcpy(file->file.name, fno.fname);
				file->file.attr = fno.fattrib;
				file->file.op = &vsf_fatfs_fsop;
				file->file.size = fno.fsize;

				__fatfs.cdir = dir->obj.sclust;
				if (file->file.attr & VSFILE_ATTR_DIRECTORY)
				{
					DIR *dp = file->entry.d;
					if (f_opendir(dp, file->file.name))
						return;
				}
				else
				{
					FIL *fp = file->entry.f;
					if (f_open(fp, file->file.name, 1))
						return;
				}

				fatfs->getchild.file = file;
			}
			break;
		}
	}
}

static vsf_err_t vsf_fatfs_getchild(struct vsfsm_pt_t *pt, vsfsm_evt_t evt,
		struct vsfile_t *dir, char *name, uint32_t idx, struct vsfile_t **file)
{
	struct vsf_fatfs_t *fatfs = (struct vsf_fatfs_t *)pt->user_data;

	vsfsm_pt_begin(pt);
	*file = NULL;
	if (vsfsm_crit_enter(&fatfs->crit, pt->sm))
		vsfsm_pt_wfe(pt, VSFSM_EVT_USER);

	fatfs->getchild.dir = ((struct vsf_fatfs_file_t *)dir)->entry.d;
	if (!fatfs->getchild.dir)
		fatfs->getchild.dir = &fatfs->root;
	fatfs->getchild.name = name;

	fatfs->pending_sm = pt->sm;
	fatfs->thread.priv = fatfs;
	fatfs->thread.op.on_run = vsf_fatfs_run_getchild;
	vsfsm_thread_start(&fatfs->thread);
	vsfsm_pt_wfe(pt, VSFSM_EVT_USER);
	*file = &fatfs->getchild.file->file;

	vsfsm_crit_leave(&fatfs->crit);
	vsfsm_pt_end(pt);
	return *file ? VSFERR_NONE : VSFERR_FAIL;
}

void vsf_fatfs_run_read(struct vsfsm_thread_t *thread)
{
	struct vsf_fatfs_t *fatfs = (struct vsf_fatfs_t *)thread->priv;
	struct vsf_fatfs_file_t *file = fatfs->close.file;

	fatfs->rw.rw_size = 0;
	if (!f_lseek(file->entry.f, fatfs->rw.offset))
		f_read(file->entry.f, fatfs->rw.buff, fatfs->rw.size, &fatfs->rw.rw_size);
}

static vsf_err_t vsf_fatfs_read(struct vsfsm_pt_t *pt, vsfsm_evt_t evt,
		struct vsfile_t *file, uint64_t offset, uint32_t size, uint8_t *buff,
		uint32_t *rsize)
{
	struct vsf_fatfs_t *fatfs = (struct vsf_fatfs_t *)pt->user_data;

	vsfsm_pt_begin(pt);
	if (vsfsm_crit_enter(&fatfs->crit, pt->sm))
		vsfsm_pt_wfe(pt, VSFSM_EVT_USER);

	fatfs->rw.file = (struct vsf_fatfs_file_t *)file;
	fatfs->rw.buff = buff;
	fatfs->rw.offset = offset;
	fatfs->rw.size = size;

	fatfs->pending_sm = pt->sm;
	fatfs->thread.priv = fatfs;
	fatfs->thread.op.on_run = vsf_fatfs_run_read;
	vsfsm_thread_start(&fatfs->thread);
	vsfsm_pt_wfe(pt, VSFSM_EVT_USER);
	if (rsize != NULL)
		*rsize = fatfs->rw.rw_size;

	vsfsm_crit_leave(&fatfs->crit);
	vsfsm_pt_end(pt);
	return VSFERR_NONE;
}

void vsf_fatfs_run_write(struct vsfsm_thread_t *thread)
{
	struct vsf_fatfs_t *fatfs = (struct vsf_fatfs_t *)thread->priv;
	struct vsf_fatfs_file_t *file = fatfs->close.file;

	fatfs->rw.rw_size = 0;
	if (!f_lseek(file->entry.f, fatfs->rw.offset))
		f_write(file->entry.f, fatfs->rw.buff, fatfs->rw.size, &fatfs->rw.rw_size);
}

static vsf_err_t vsf_fatfs_write(struct vsfsm_pt_t *pt, vsfsm_evt_t evt,
		struct vsfile_t *file, uint64_t offset, uint32_t size, uint8_t *buff,
		uint32_t *wsize)
{
	struct vsf_fatfs_t *fatfs = (struct vsf_fatfs_t *)pt->user_data;

	vsfsm_pt_begin(pt);
	if (vsfsm_crit_enter(&fatfs->crit, pt->sm))
		vsfsm_pt_wfe(pt, VSFSM_EVT_USER);

	fatfs->rw.file = (struct vsf_fatfs_file_t *)file;
	fatfs->rw.buff = buff;
	fatfs->rw.offset = offset;
	fatfs->rw.size = size;

	fatfs->pending_sm = pt->sm;
	fatfs->thread.priv = fatfs;
	fatfs->thread.op.on_run = vsf_fatfs_run_read;
	vsfsm_thread_start(&fatfs->thread);
	vsfsm_pt_wfe(pt, VSFSM_EVT_USER);
	if (wsize != NULL)
		*wsize = fatfs->rw.rw_size;

	vsfsm_crit_leave(&fatfs->crit);
	vsfsm_pt_end(pt);
	return VSFERR_NONE;
}

void vsf_fatfs_run_close(struct vsfsm_thread_t *thread)
{
	struct vsf_fatfs_t *fatfs = (struct vsf_fatfs_t *)thread->priv;
	struct vsf_fatfs_file_t *file = fatfs->close.file;

	if (file->file.attr & VSFILE_ATTR_DIRECTORY)
		f_closedir(file->entry.d);
	else
		f_close(file->entry.f);
}

static vsf_err_t vsf_fatfs_close(struct vsfsm_pt_t *pt, vsfsm_evt_t evt,
		struct vsfile_t *file)
{
	struct vsf_fatfs_t *fatfs = (struct vsf_fatfs_t *)pt->user_data;

	vsfsm_pt_begin(pt);

	fatfs->close.file = (struct vsf_fatfs_file_t *)file;

	fatfs->pending_sm = pt->sm;
	fatfs->thread.priv = fatfs;
	fatfs->thread.op.on_run = vsf_fatfs_run_close;
	vsfsm_thread_start(&fatfs->thread);
	vsfsm_pt_wfe(pt, VSFSM_EVT_USER);
	vsf_bufmgr_free(file);

	vsfsm_pt_end(pt);
	return VSFERR_NONE;
}

const struct vsfile_fsop_t vsf_fatfs_fsop =
{
	// mount / unmount
	.mount = vsf_fatfs_mount,
	.unmount = vsfile_dummy_mount,
	// f_op
	.f_op.close = vsf_fatfs_close,
	.f_op.read = vsf_fatfs_read,
	.f_op.write = vsf_fatfs_write,
	// d_op
	.d_op.getchild = vsf_fatfs_getchild,
};
